/**
 *  File: stat.c
 *
 *  Copyright (C) 2008 Du XiaoGang <dugang@188.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 */

#include "common.h"

static int compression(const char *inbuf, size_t inlen, char *outbuf, size_t outlen);

/* for statistic */
time_t stat_begin_time;
int begin_idx = 0, stat_count = 0, ncpu;
struct statistic_t stat_arr[STAT_COUNT_MAX];
double mem_total = 0.0;  /* in mbytes */

struct entity_t *
make_statpage(void)
{
#define ITEM_ADD(format, item, color, range, desc) \
        do { \
            int tmpi; \
            /* item begin */ \
            len = snprintf(buf, sizeof(buf), statpage_item_begin, 1); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
            /* data */ \
            tmpi = stat_count < STAT_COUNT_MAX ? stat_count : STAT_COUNT_MAX; \
            for (i = 0; i < tmpi; i++) { \
                len = snprintf(buf, sizeof(buf), format, item); \
                strcat(statpage_inbuf, buf); \
                inlen += len; \
            } \
            /* item end */ \
            len = snprintf(buf, sizeof(buf), statpage_item_end, color, range, "false", "", desc); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
        } while (0)

#define ITEM_ADD2(format1, item1, format2, item2, color, range, lines, desc) \
        do { \
            int tmpi; \
            /* item begin */ \
            len = snprintf(buf, sizeof(buf), statpage_item_begin, 2); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
            /* data0 */ \
            tmpi = stat_count < STAT_COUNT_MAX ? stat_count : STAT_COUNT_MAX; \
            for (i = 0; i < tmpi; i++) { \
                len = snprintf(buf, sizeof(buf), format1, item1); \
                strcat(statpage_inbuf, buf); \
                inlen += len; \
            } \
            /* item mid */ \
            len = snprintf(buf, sizeof(buf), statpage_item_mid, 1); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
            /* data1 */ \
            for (i = 0; i < tmpi; i++) { \
                len = snprintf(buf, sizeof(buf), format2, item2); \
                strcat(statpage_inbuf, buf); \
                inlen += len; \
            } \
            /* item end */ \
            len = snprintf(buf, sizeof(buf), statpage_item_end, color, range, "true", lines, desc); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
        } while (0)

#define ITEM_ADD3(format1, item1, format2, item2, format3, item3, color, range, lines, desc) \
        do { \
            int tmpi; \
            /* item begin */ \
            len = snprintf(buf, sizeof(buf), statpage_item_begin, 3); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
            /* data0 */ \
            tmpi = stat_count < STAT_COUNT_MAX ? stat_count : STAT_COUNT_MAX; \
            for (i = 0; i < tmpi; i++) { \
                len = snprintf(buf, sizeof(buf), format1, item1); \
                strcat(statpage_inbuf, buf); \
                inlen += len; \
            } \
            /* item mid */ \
            len = snprintf(buf, sizeof(buf), statpage_item_mid, 1); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
            /* data1 */ \
            for (i = 0; i < tmpi; i++) { \
                len = snprintf(buf, sizeof(buf), format2, item2); \
                strcat(statpage_inbuf, buf); \
                inlen += len; \
            } \
            /* item mid */ \
            len = snprintf(buf, sizeof(buf), statpage_item_mid, 2); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
            /* data2 */ \
            for (i = 0; i < tmpi; i++) { \
                len = snprintf(buf, sizeof(buf), format3, item3); \
                strcat(statpage_inbuf, buf); \
                inlen += len; \
            } \
            /* item end */ \
            len = snprintf(buf, sizeof(buf), statpage_item_end, color, range, "true", lines, desc); \
            strcat(statpage_inbuf, buf); \
            inlen += len; \
        } while (0)

    struct entity_t *rets;
    int i, len, inlen, outlen;
    char buf[4096];
    /* static varibles */
    static int header_len = 0;
    static char statpage_inbuf[STATPAGE_MAX], statpage_outbuf[STATPAGE_MAX + 20],
                header[4096];
    static char *statpage_begin =
        "<html>\r\n"
        "    <head>\r\n"
        "        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n"
        "        <title>TestHttpd运行状态信息统计</title>\r\n"
        "    </head>\r\n"
        "    <body>\r\n"
        "        <table align=\"center\" border=\"0\">\r\n"
        "            <tr><td align=\"center\">\r\n"
        "                <hr>\r\n"
        "                <h1>TestHttpd运行状态信息统计</h1>\r\n"
        "                <hr>\r\n"
        "            </td></tr>\r\n"
        "            <tr><td align=\"right\">\r\n"
        "                <b>系统CPU总数: %d</b>\r\n"
        "            </td></tr>\r\n"
        "            <tr><td align=\"right\">\r\n"
        "                <b>系统内存总数: %dMBytes</b>\r\n"
        "            </td></tr>\r\n"
        "            <tr><td align=\"right\">\r\n"
        "                <b>本次统计时间: %d秒</b>\r\n"
        "                <hr>\r\n"
        "            </td></tr>\r\n"
        "\r\n";
    static char *statpage_item_begin = 
        "            <tr><td align=\"center\">\r\n"
        "                <applet code=\"com.objectplanet.chart.ChartApplet.class\" codebase=\"/local/\" archive=\"chart.jar\" width=\"800\" height=\"320\">\r\n"
        "                <param name=\"chart\" value=\"line\">\r\n"
        "                <param name=\"seriesCount\" value=\"%d\">\r\n"
        "                <param name=\"sampleValues_0\" value=\"";
    static char *statpage_item_mid = 
        "\">\r\n"
        "                <param name=\"sampleValues_%d\" value=\"";
    static char *statpage_item_end = 
        "\">\r\n"
        "                <param name=\"sampleColors\" value=\"%s\">\r\n"
        "                <param name=\"range\" value=\"%s\">\r\n"
        "                <param name=\"legendOn\" value=\"%s\">\r\n"
        "                <param name=\"legendPosition\" value=\"bottom\">\r\n"
        "                <param name=\"legendLabels\" value=\"%s\">\r\n"
        "                </applet>\r\n"
        "            </td></tr>\r\n"
        "            <tr><td align=\"center\">\r\n"
        "                <b>%s</b>\r\n"
        "                <hr>\r\n"
        "            </td></tr>\r\n"
        "\r\n";
    static char *statpage_end =
        "            <tr><td align=\"center\">\r\n"
        "                <b>本页面由TestHttpd %s自动生成</b>\r\n"
        "            </td></tr>\r\n"
        "            <tr><td align=\"center\">\r\n"
        "                <b>TestHttpd主页\r\n"
        "                    <a href=\"http://testhttpd.googlecode.com\">http://testhttpd.googlecode.com</a>\r\n"
        "                </b>\r\n"
        "            </td></tr>\r\n"
        "            <tr><td align=\"center\">\r\n"
        "                <b>作者:Du XiaoGang\r\n"
        "                    <a href=\"mailto:dugang@188.com\">EMail</a>\r\n"
        "                    <a href=\"http://inside2004.cublog.cn\">Blog</a>\r\n"
        "                </b>\r\n"
        "                <hr>\r\n"
        "            </td></tr>\r\n"
        "        </table>\r\n"
        "    </body>\r\n"
        "</html>\r\n";

    /* make page body */
    /* page begin */
    statpage_inbuf[0] = '\0';
    len = snprintf(buf, sizeof(buf), statpage_begin, ncpu, (int)(mem_total + 0.5), 
                   (stat_count < STAT_COUNT_MAX ? stat_count : STAT_COUNT_MAX) * STAT_INTERVAL);
    strcat(statpage_inbuf, buf);
    inlen = len;

    /* page items */
    /* for cpu usage */
    ITEM_ADD2("%d,", stat_arr[(begin_idx + i) % STAT_COUNT_MAX].cpu_usedp, 
              "%d,", stat_arr[(begin_idx + i) % STAT_COUNT_MAX].cpu_httpdp, 
              "#FF0000,#00FF00", "100", 
              "系统CPU使用率(%),TestHttpd使用CPU比率(%)", "CPU使用率统计");
    /* for memory usage */
    ITEM_ADD3("%d,", stat_arr[(begin_idx + i) % STAT_COUNT_MAX].mem_usedp, 
              "%d,", stat_arr[(begin_idx + i) % STAT_COUNT_MAX].mem_cachedp, 
              "%d,", stat_arr[(begin_idx + i) % STAT_COUNT_MAX].mem_httpdp, 
              "#FF0000,#00FF00,#0000FF", "100", 
              "系统内存使用率(%),Cache使用内存比率(%),TestHttpd使用内存比率(%)", 
              "内存使用率统计");
    /* for max_conns */
    ITEM_ADD("%d,", stat_arr[(begin_idx + i) % STAT_COUNT_MAX].max_conns, 
             "#FF0000", "1000", "并发连接数统计");
    /* for accepted */
    ITEM_ADD("%d,", stat_arr[(begin_idx + i) % STAT_COUNT_MAX].accepted / STAT_INTERVAL, 
             "#FF0000", "10000", "每秒接受新连接数统计");
    /* for throughout */
    ITEM_ADD2("%0.1f,", 
              ((double)(stat_arr[(begin_idx + i) % STAT_COUNT_MAX].received)) / 1024 / 1024 / STAT_INTERVAL,
              "%0.1f,", 
              ((double)(stat_arr[(begin_idx + i) % STAT_COUNT_MAX].sent)) / 1024 / 1024 / STAT_INTERVAL,
              "#FF0000,#00FF00", "15", 
              "上行流量统计(MBytes/S),下行流量统计(MBytes/S)", "吞吐量统计");

    /* page end */
    len = snprintf(buf, sizeof(buf), statpage_end, VERSION);
    strcat(statpage_inbuf, buf);
    inlen += len;

    /* compress, reserved 20 bytes for chunked-encoding  */
    outlen = compression(statpage_inbuf, inlen, statpage_outbuf + 10, 
                         sizeof(statpage_outbuf) - 20);

    /* make chunked header */
    snprintf(buf, sizeof(buf), "%08X\r\n", outlen);
    memcpy(statpage_outbuf, buf, 10);
    /* append chunked tailer */
    memcpy(statpage_outbuf + 10 + outlen, "\r\n0\r\n\r\n", 7);

    /* entity_t and body */
    rets = malloc(sizeof(struct entity_t));
    if (rets == NULL)
        return NULL;
    rets->body_length = outlen + 10 + 7;
    rets->body_dyn = 1;
    rets->body = malloc(rets->body_length);
    if (rets->body == NULL) {
        free(rets);
        return NULL;
    }
    memcpy(rets->body, statpage_outbuf, rets->body_length);

    /* header */
    if (header_len == 0) {
        header_len = snprintf(header, sizeof(header),
                              "HTTP/1.1 200 OK\r\n"
                              "Server: %s/%s\r\n"
                              "Content-Type: text/html; charset=utf-8\r\n"
                              "Content-Encoding: gzip\r\n"
                              "Transfer-Encoding: chunked\r\n"
                              "Cache-Control: no-cache\r\n"
                              "Accept-Ranges: none\r\n"
                              "Connection: close\r\n"
                              "\r\n", 
                              SOFTWARE, VERSION);
    }
    rets->header = header;
    rets->header_length = header_len;
    rets->header_dyn = 0;

    return rets;
}

static int
compression(const char *inbuf, size_t inlen, char *outbuf, size_t outlen)
{
    int ret;
    z_stream strm;
    char strbuf[100];
    
    /* allocate deflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    //////////////////////////////////////////////////
    // for zlib/deflate
    //ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);
    ////////////////////////////////////////////////////////////
    // for gzip/deflate
    ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
                       31, 8, Z_DEFAULT_STRATEGY);
    if (ret != Z_OK) {
        snprintf(strbuf, sizeof(strbuf), "deflateInit2() return %d", ret);
        ERR(strbuf);
        exit(1);
    }

    strm.avail_in = inlen;
    strm.next_in = (unsigned char *)inbuf;
    strm.avail_out = outlen;
    strm.next_out = (unsigned char *)outbuf;
    ret = deflate(&strm, Z_FINISH);    /* no bad return value */
    if (ret != Z_STREAM_END) {
        ERR("outbuf is too small");
        exit(1);
    }
    ret = outlen - strm.avail_out;

    /* clean up and return */
    (void) deflateEnd(&strm);
    return ret;
}

void
get_cpustat(pid_t pid, int *id, int *pu, int *ps, struct timeval *tv)
{
    FILE *fp;
    int n, ret;
    char *tok, *saveptr, path[1024], linebuf[1024];
    
    /* read stat information */
    fp = fopen("/proc/stat", "r");
    if (fp == NULL) {
        ERR("null");
        exit(1);
    }

    /* "cpu" */
    fgets(linebuf, sizeof(linebuf), fp);
    n = 0;
    tok = strtok_r(linebuf, " \t", &saveptr);
    while (tok != NULL) {
        if (n == 4) {
            *id = atoi(tok);
            break;
        }
        tok = strtok_r(NULL, " \t", &saveptr);
        n++;
    }
    /* close */
    fclose(fp);
    if (n != 4) {
        ERR("null");
        exit(1);
    }

    /* read pid's stat information */
    snprintf(path, sizeof(path), "/proc/%d/stat", (int)pid);
    fp = fopen(path, "r");
    if (fp == NULL) {
        ERR("null");
        exit(1);
    }

    fgets(linebuf, sizeof(linebuf), fp);
    n = 0;
    tok = strtok_r(linebuf, " \t", &saveptr);
    while (tok != NULL) {
        if (n == 13) {
            *pu = atoi(tok);
        } else if (n == 14) {
            *ps = atoi(tok);
            break;
        }
        tok = strtok_r(NULL, " \t", &saveptr);
        n++;
    }
    /* close */
    fclose(fp);
    if (n != 14) {
        ERR("null");
        exit(1);
    }

    /* get begin time for stat */
    ret = gettimeofday(tv, NULL);
    if (ret == -1) {
        ERR("null");
        exit(1);
    }
}

void 
get_memusage(pid_t pid, double *total, double *used, double *cached, double *process)
{
    FILE *fp;
    char *p, path[1024], linebuf[1024];
    static int get_total = 0;
    static double total_memory;

    /* read memory information */
    fp = fopen("/proc/meminfo", "r");
    if (fp == NULL) {
        ERR("null");
        exit(1);
    }

    /* "MemTotal" */
    fgets(linebuf, sizeof(linebuf), fp);
    if (!get_total) {
        p = linebuf;
        p += strspn(p, " \t");
        p = strpbrk(p, " \t");
        if (!p) {
            ERR(linebuf);
            fclose(fp);
            exit(1);
        }
        p += strspn(p, " \t");
        total_memory = ((double)atoi(p)) / 1024;
        /* total_memory shouldn't change */
        get_total = 1;
    }
    *total = total_memory;

    /* "MemFree" */
    fgets(linebuf, sizeof(linebuf), fp);
    p = linebuf;
    p += strspn(p, " \t");
    p = strpbrk(p, " \t");
    if (!p) {
        ERR(linebuf);
        fclose(fp);
        exit(1);
    }
    p += strspn(p, " \t");
    *used = *total - ((double)atoi(p)) / 1024;

    /* "Buffers" */
    fgets(linebuf, sizeof(linebuf), fp);
    /* "Cached" */
    fgets(linebuf, sizeof(linebuf), fp);
    p = linebuf;
    p += strspn(p, " \t");
    p = strpbrk(p, " \t");
    if (!p) {
        ERR(linebuf);
        fclose(fp);
        exit(1);
    }
    p += strspn(p, " \t");
    *cached = ((double)atoi(p)) / 1024;
    fclose(fp);

    /* get process memory information */
    snprintf(path, sizeof(path), "/proc/%d/statm", (int)pid);
    fp = fopen(path, "r");
    if (!fp) {
        ERR("null");
        exit(1);
    }
    /* the first line contains "rss" at col 2 */
    fgets(linebuf, sizeof(linebuf), fp);
    p = linebuf;
    p += strspn(p, " \t");
    p = strpbrk(p, " \t");
    if (!p) {
        ERR(linebuf);
        fclose(fp);
        exit(1);
    }
    p += strspn(p, " \t");
    *process = ((double)atoi(p) * 4) / 1024;
    fclose(fp);
}

struct entity_t *
make_chartjar(void)
{
    struct entity_t *rets;
    int len;
    time_t now;
    char nowbuf[100], expbuf[100], buf[4096];
    const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT";

    extern char chart_jar[];
    extern int chart_jar_len;

    /* entity_t and body */
    rets = malloc(sizeof(struct entity_t));
    if (rets == NULL)
        return NULL;

    now = time(NULL);
    strftime(nowbuf, sizeof(nowbuf), rfc1123fmt, gmtime(&now));
    /* chart.jar will be expired after one year. */
    now += 365 * 24 * 3600; 
    strftime(expbuf, sizeof(expbuf), rfc1123fmt, gmtime(&now));

    /* header */
    len = snprintf(buf, sizeof(buf),
                   "HTTP/1.1 200 OK\r\n"
                   "Server: %s/%s\r\n"
                   "Content-Type: application/x-java-archive\r\n"
                   "Content-Length: %d\r\n"
                   "Last-Modified: Wed, 13 Feb 2008 10:41:12 GMT\r\n"
                   "Date: %s\r\n"
                   "Expires: %s\r\n"
                   "Accept-Ranges: none\r\n"
                   "Connection: close\r\n"
                   "\r\n", 
                   SOFTWARE, VERSION, chart_jar_len, nowbuf, expbuf);
    rets->header = strdup(buf);
    if (rets->header == NULL) {
        free(rets);
        return NULL;
    }
    rets->header_length = len;
    rets->header_dyn = 1;

    /* body */
    rets->body = chart_jar;
    rets->body_length = chart_jar_len;
    rets->body_dyn = 0;

    return rets;
}

int
get_cpucount(void)
{
    FILE *fp;
    int n = 0;
    char *p, linebuf[1024];
    
    /* read stat information */
    fp = fopen("/proc/stat", "r");
    if (fp == NULL) {
        ERR("null");
        return -1;
    }
    
    /* "cpu" */
    p = fgets(linebuf, sizeof(linebuf), fp);
    if (p == NULL || strncasecmp(p, "cpu", 3) != 0) {
        ERR(linebuf);
        fclose(fp);
        return -1;
    }
    /* cpu0 */
    p = fgets(linebuf, sizeof(linebuf), fp);
    if (p == NULL || strncasecmp(p, "cpu", 3) != 0) {
        ERR(linebuf);
        fclose(fp);
        return -1;
    }
    n++;
    /* cpu1... */
    while ((p = fgets(linebuf, sizeof(linebuf), fp)) != NULL) {
        if (strncasecmp(p, "cpu", 3) == 0)
            n++;
        else
            break;
    }

    /* close */
    fclose(fp);

    return n;
}
